Close a Position
- Rust
- TypeScript Kit
- TypeScript Legacy
Once you've provided liquidity in a pool, there may come a time when you want to close your position entirely. The SDK allows you to fully remove liquidity from the pool, collect any outstanding fees and rewards, and close the position. This is useful when you want to exit the pool, either to realize profits or to reallocate capital to other opportunities.
This guide explains how to use the SDK to close a position.
1. Overview of Closing a Position
When using the SDK to fully close a liquidity position, you generate all the necessary instructions. It performs the following key actions:
- Collect Fees: Retrieves any fees earned from trades involving your liquidity.
- Collect Rewards: Retrieves any rewards you've accumulated for the pool.
- Decrease Liquidity: Removes any remaining liquidity in the position.
- Close Position: Closes the position and returns the tokens in your account.
2. Getting Started Guide
Closing a Position
To close a position and withdraw all liquidity, follow these steps:
- RPC Client: Use a Solana RPC client to interact with the blockchain.
- Position Mint: Provide the mint address of the NFT representing your position. This NFT serves as proof of ownership and represents the liquidity in the position.
- Parameters for Liquidity: Define the parameters for decreasing liquidity. This can be specified as a liquidity amount or as specific token amounts.
- Slippage Tolerance: Set the maximum slippage tolerance (optional, defaults to 1%). Slippage refers to the difference between the expected token amounts you receive when closing a position and the actual amounts returned to your wallet. A lower slippage tolerance reduces the risk of receiving fewer tokens than expected but may lead to failed transactions if the market moves too quickly. For example, if you expect to receive 100 units of Token A and 1,000 units of Token B when closing your position, with a 1% slippage tolerance, the minimum amounts returned would be 99 Token A and 990 Token B.
- Authority: This can be your wallet, which will fund the pool initialization. If the authority is not specified, the default wallet will be used. You can configure the default wallet through the SDK.
- Create Instructions: Use the appropriate function to generate the necessary instructions.
use crate::utils::load_wallet;
use orca_whirlpools::{
close_position_instructions, set_whirlpools_config_address, WhirlpoolsConfigInput,
};
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;
use solana_sdk::signature::Signer;
use orca_tx_sender::{
build_and_send_transaction,
set_rpc, get_rpc_client
};
use solana_sdk::commitment_config::CommitmentLevel;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
set_rpc("https://api.devnet.solana.com").await?;
set_whirlpools_config_address(WhirlpoolsConfigInput::SolanaDevnet).unwrap();
let wallet = load_wallet();
let rpc = get_rpc_client()?;
let position_mint_address =
Pubkey::from_str("HqoV7Qv27REUtmd9UKSJGGmCRNx3531t33bDG1BUfo9K").unwrap();
let result = close_position_instructions(
&rpc,
position_mint_address,
Some(100),
Some(wallet.pubkey()),
)
.await?;
println!("Quote token max B: {:?}", result.quote.token_est_b);
println!("Fees Quote: {:?}", result.fees_quote);
println!("Rewards Quote: {:?}", result.rewards_quote);
println!("Number of Instructions: {}", result.instructions.len());
let signature = build_and_send_transaction(
result.instructions,
&[&wallet],
Some(CommitmentLevel::Confirmed),
None, // No address lookup tables
).await?;
println!("Close position transaction sent: {}", signature);
Ok(())
}
3. Usage Example
Suppose your trading strategy predicts that the current price range will lead to divergence loss, and you need to close the position to avoid further losses. Using the SDK, you can generate the instructions to collect all accumulated fees, rewards, and remove liquidity to prevent further losses.
4. Next Steps
After closing a position, you can:
- Open a New Position: If you want to redeploy your capital, you can open a new position in a different price range or pool.
- Fetch All Positions: Check all your remaining positions to manage your overall liquidity strategy.
- Reinvest the funds from the closed position into other opportunities based on market conditions.
Once you've provided liquidity in a pool, there may come a time when you want to close your position entirely. The SDK allows you to fully remove liquidity from the pool, collect any outstanding fees and rewards, and close the position. This is useful when you want to exit the pool, either to realize profits or to reallocate capital to other opportunities.
This guide explains how to use the SDK to close a position.
1. Overview of Closing a Position
When using the SDK to fully close a liquidity position, you generate all the necessary instructions. It performs the following key actions:
- Collect Fees: Retrieves any fees earned from trades involving your liquidity.
- Collect Rewards: Retrieves any rewards you've accumulated for the pool.
- Decrease Liquidity: Removes any remaining liquidity in the position.
- Close Position: Closes the position and returns the tokens in your account.
2. Getting Started Guide
Closing a Position
To close a position and withdraw all liquidity, follow these steps:
- RPC Client: Use a Solana RPC client to interact with the blockchain.
- Position Mint: Provide the mint address of the NFT representing your position. This NFT serves as proof of ownership and represents the liquidity in the position.
- Parameters for Liquidity: Define the parameters for decreasing liquidity. This can be specified as a liquidity amount or as specific token amounts.
- Slippage Tolerance: Set the maximum slippage tolerance (optional, defaults to 1%). Slippage refers to the difference between the expected token amounts you receive when closing a position and the actual amounts returned to your wallet. A lower slippage tolerance reduces the risk of receiving fewer tokens than expected but may lead to failed transactions if the market moves too quickly. For example, if you expect to receive 100 units of Token A and 1,000 units of Token B when closing your position, with a 1% slippage tolerance, the minimum amounts returned would be 99 Token A and 990 Token B.
- Authority: This can be your wallet, which will fund the pool initialization. If the authority is not specified, the default wallet will be used. You can configure the default wallet through the SDK.
- Create Instructions: Use the appropriate function to generate the necessary instructions.
import { closePositionInstructions, setWhirlpoolsConfig } from '@orca-so/whirlpools';
import { createSolanaRpc, devnet, address } from '@solana/kit';
import { loadWallet } from './utils';
await setWhirlpoolsConfig('solanaDevnet');
const devnetRpc = createSolanaRpc(devnet('https://api.devnet.solana.com'));
const wallet = await loadWallet();
const positionMint = address("HqoV7Qv27REUtmd9UKSJGGmCRNx3531t33bDG1BUfo9K");
const { instructions, quote, feesQuote, rewardsQuote, callback: sendTx } = await closePosition(
devnetRpc,
positionMint,
100,
wallet
);
// Use the callback to submit the transaction
const txId = await sendTx();
console.log(`Quote token max B: ${quote.tokenEstB}`);
console.log(`Fees owed token A: ${feesQuote.feeOwedA}`);
console.log(`Rewards '1' owed: ${rewardsQuote.rewards[0].rewardsOwed}`);
console.log(`Number of instructions:, ${instructions.length}`);
console.log(`Transaction ID: ${txId}`);
3. Usage Example
Suppose your trading strategy predicts that the current price range will lead to divergence loss, and you need to close the position to avoid further losses. Using the SDK, you can generate the instructions to collect all accumulated fees, rewards, and remove liquidity to prevent further losses.
4. Next Steps
After closing a position, you can:
- Open a New Position: If you want to redeploy your capital, you can open a new position in a different price range or pool.
- Fetch All Positions: Check all your remaining positions to manage your overall liquidity strategy.
- Reinvest the funds from the closed position into other opportunities based on market conditions.
Closing a Position
To close a position, you must first withdraw all liquidity and collect all fees and rewards from the position. You can then call the closePosition
instruction to close and burn the position NFT.
The parameters of closePosition
are identical to the ones in openPosition
.
Whirlpool Client - Sample Code
The WhirlpoolClient
version of closePosition
will automatically call decrease_liquidity
and close_position
for you. Note that you still have to manually call collect_fees
and collect_reward
to make sure the position is empty.
const client = new WhirlpoolClient(context, fetcher);
const poolAddress = PDAUtil.getPool(...)
const positionAddress = PDAUtil.getPosition(...);
const pool = client.getPool(poolAddress);
const tx = await pool.closePosition(positionAddress, Percentage.fromFraction(1, 100))
await tx.buildAndExecute();
Instruction - Sample Code
const poolAddress = PDAUtil.getPool(...)
const positionAddress = PDAUtil.getPosition(...);
const position = await fetcher.getPosition(positionAddress);
// Must manually call update_fee_and_rewards -> collect_fees -> collect_rewards
// Must manually call decrease_liquidity
const tx = await toTx(ctx, WhirlpoolIx.closePositionTx(ctx, {
positionAuthority: ctx.wallet.publicKey,
receiver: ctx.wallet.publicKey,
positionTokenAccount,
position: positionAddress,
positionMint: position.positionMint,
}))
await tx.buildAndExecute();
Common Errors
ClosePositionNotEmpty
(0x1775) - Position still has liquidity in it. Withdraw all before calling this instruction.